#!/usr/bin/env bash
set -eo pipefail; [[ $SSHCOMMAND_TRACE ]] && set -x
shopt -s nocasematch # For case insensitive string matching, for the first parameter

if [[ -f /etc/defaults/sshcommand ]]; then
  # shellcheck disable=SC1091
  source /etc/defaults/sshcommand
fi

declare SSHCOMMAND_VERSION=""
declare SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT=${SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT:="true"}
declare SSHCOMMAND_CHECK_DUPLICATE_NAME=${SSHCOMMAND_CHECK_DUPLICATE_NAME:="true"}

cmd-help() {
  declare desc="Shows help information for a command"
  declare args="$*"
  if [[ "$args" ]]; then
    for cmd; do true; done # last arg
    local fn="sshcommand-$cmd"
    fn-info "$fn" 1
  fi
}

fn-args() {
  declare desc="Inspect a function's arguments"
  local argline
  argline=$(type "$1" | grep declare | grep -v "declare desc" | head -1)
  echo -e "${argline// /"\\n"}" | awk -F= '/=/{print "<"$1">"}' | tr "\\n" " "
}

fn-desc() {
  declare desc="Inspect a function's description"
  desc=""
  eval "$(type "$1" | grep desc | head -1)"; echo $desc
}

fn-info() {
  declare desc="Inspects a function"
  declare fn="$1" showsource="$2"
  local fn_name="${1//sshcommand-/}"
  echo "$fn_name $(fn-args "$fn")"
  echo "  $(fn-desc "$fn")"
  echo
  if [[ "$showsource" ]]; then
    type "$fn" | tail -n +2
    echo
  fi
}

fn-print-os-id() {
  declare desc="Returns the release id of the operating system"
  local OSRELEASE="${SSHCOMMAND_OSRELEASE:="/etc/os-release"}"
  if [[ -f $OSRELEASE ]]; then
    sed -n 's#^ID=\(.*\)#\1#p' "$OSRELEASE" | tr -d '"'
  else
    echo unknown
  fi
  return 0
}

fn-adduser() {
  declare desc="Add a user to the system"
  local l_user l_platform

  l_user=$1
  l_platform="$(fn-print-os-id)"
  case $l_platform in
    alpine)
      adduser -D -g "" -s /bin/bash "$l_user"
      passwd -u "$l_user"
      ;;
    debian*|ubuntu|raspbian*)
      adduser --disabled-password --gecos "" "$l_user"
      ;;
    arch|amzn)
      useradd -m -s /bin/bash "$l_user"
      usermod -L -aG "$l_user" "$l_user"
      ;;
    *)
      useradd -m -s /bin/bash "$l_user"
      groupadd "$l_user"
      usermod -L -aG "$l_user" "$l_user"
      ;;
  esac
}

log-fail() {
  declare desc="Log fail formatter"
  echo "$@" 1>&2
  exit 1
}

log-verbose() {
  declare desc="Log verbose formatter"
  if [[ -n "$SSHCOMMAND_VERBOSE_OUTPUT" ]]; then
    echo "$@"
  fi
}

sshcommand-create() {
  declare desc="Creates a local system user and installs sshcommand skeleton"
  declare USER="$1" COMMAND="$2"
  local USERHOME

  if [[ -z "$USER" ]] || [[ -z "$COMMAND" ]]; then
    log-fail "Usage: sshcommand create" "$(fn-args "sshcommand-create")"
  fi

  if id -u "$USER" >/dev/null 2>&1; then
    log-verbose "User '$USER' already exists"
  else
    fn-adduser "$USER"
  fi

  USERHOME=$(sh -c "echo ~$USER")
  mkdir -p "$USERHOME/.ssh"
  touch "$USERHOME/.ssh/authorized_keys"
  echo "$COMMAND" > "$USERHOME/.sshcommand"
  chown -R "$USER" "$USERHOME"
}

sshcommand-acl-add() {
  declare desc="Adds named SSH key to user from STDIN or argument"
  declare USER="$1" NAME="$2" KEY_FILE="$3"
  local ALLOWED_KEYS FINGERPRINT KEY KEY_FILE KEY_PREFIX NEW_KEY USERHOME

  if [[ -z "$USER" ]] || [[ -z "$NAME" ]]; then
    log-fail "Usage: sshcommand acl-add" "$(fn-args "sshcommand-acl-add")"
  fi

  getent passwd "$USER" > /dev/null || false
  USERHOME=$(sh -c "echo ~$USER")

  NEW_KEY=$(grep "NAME=\\\\\"$NAME"\\\\\" "$USERHOME/.ssh/authorized_keys" || true)
  if [[ "$SSHCOMMAND_CHECK_DUPLICATE_NAME" == "true" ]] && [[ -n "$NEW_KEY" ]]; then
    log-fail "Duplicate ssh key name"
  fi

  if [[ -z "$KEY_FILE" ]]; then
    KEY_FILE=$(mktemp)
    KEY=$(tee "$KEY_FILE")
    trap 'rm -f "$KEY_FILE"' INT EXIT
  else
    KEY=$(cat "$KEY_FILE")
  fi

  local count
  count="$(wc -l <<< "$KEY")"
  [[ "$count" -eq 1 ]] || log-fail "Too many keys provided, set one per invocation of sshcommand acl-add <USER> <NAME>"

  FINGERPRINT=$(ssh-keygen -lf <(echo "command=\"dummy to fail when options already exist\" $KEY") | awk '{print $2}')

  if [[ ! "$FINGERPRINT" =~ :.* ]]; then
    log-fail "Invalid ssh public key"
  fi

  if [[ "$SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT" == "true" ]] && grep -qF "$FINGERPRINT" "$USERHOME/.ssh/authorized_keys"; then
    log-fail "Duplicate ssh public key specified"
  fi

  ALLOWED_KEYS="${SSHCOMMAND_ALLOWED_KEYS:="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding"}"
  KEY_PREFIX="command=\"FINGERPRINT=$FINGERPRINT NAME=\\\"$NAME\\\" \`cat $USERHOME/.sshcommand\` \$SSH_ORIGINAL_COMMAND\",$ALLOWED_KEYS"
  echo "$KEY_PREFIX $KEY" >> "$USERHOME/.ssh/authorized_keys"
  echo "$FINGERPRINT"
}

sshcommand-acl-remove() {
  declare desc="Removes SSH key by name"
  declare USER="$1" NAME="$2"
  local USERHOME

  if [[ -z "$USER" ]] || [[ -z "$NAME" ]]; then
    log-fail "Usage: sshcommand acl-remove" "$(fn-args "sshcommand-acl-remove")"
  fi

  getent passwd "$USER" > /dev/null || false
  USERHOME=$(sh -c "echo ~$USER")

  sed --in-place "/ NAME=\\\\\"$NAME\\\\\" /d" "$USERHOME/.ssh/authorized_keys"
}

sshcommand-list() {
  declare desc="Lists SSH keys by user, an optional name and a optional output format (JSON)"
  declare userhome USER="$1" NAME="$2" OUTPUT_TYPE="${3:-$2}"
  [[ -z "$USER" ]] && log-fail "Usage: sshcommand list" "$(fn-args "sshcommand-list")"

  getent passwd "$USER" > /dev/null || log-fail "\"$USER\" is not a user on this system"
  userhome=$(sh -c "echo ~$USER")
  [[ -e "$userhome/.ssh/authorized_keys" ]] || log-fail "authorized_keys not found for $USER"
  [[ -s "$userhome/.ssh/authorized_keys" ]] || log-fail "authorized_keys is empty for $USER"

  if [[ -n "$OUTPUT_TYPE" ]] && [[ "$OUTPUT_TYPE" == "json" ]]; then
    data=$(sed --silent --regexp-extended \
      's/^command="FINGERPRINT=(\S+) NAME=(\\"|)(.*)\2 `.*",(\S+).*/{ "fingerprint": "\1", "name": "\3", "SSHCOMMAND_ALLOWED_KEYS": "\4" }/p' \
      "$userhome/.ssh/authorized_keys" | tr '\n' ',' | sed '$s/,$/\n/')

    echo "[${data}]";
  else
    OUTPUT="$(sed --silent --regexp-extended \
        's/^command="FINGERPRINT=(\S+) NAME=(\\"|)(.*)\2 `.*",(\S+).*/\1 NAME="\3" SSHCOMMAND_ALLOWED_KEYS="\4"/p' \
        "$userhome/.ssh/authorized_keys")"
    if [[ -n "$NAME" ]]; then
      echo "$OUTPUT" | grep "NAME=\"$NAME\""
    else
      echo "$OUTPUT"
    fi
  fi
}

sshcommand-help() {
  declare desc="Shows help information"
  declare COMMAND="$1"

  if [[ -n "$COMMAND" ]]; then
    cmd-help "$COMMAND"
    return 0
  fi

  echo "sshcommand ${SSHCOMMAND_VERSION}"
  echo ""
  printf "  %-10s %-30s %s\\n" "create"     "$(fn-args "sshcommand-create")"     "$(fn-desc "sshcommand-create")"
  printf "  %-10s %-30s %s\\n" "acl-add"    "$(fn-args "sshcommand-acl-add")"    "$(fn-desc "sshcommand-acl-add")"
  printf "  %-10s %-30s %s\\n" "acl-remove" "$(fn-args "sshcommand-acl-remove")" "$(fn-desc "sshcommand-acl-remove")"
  printf "  %-10s %-30s %s\\n" "list"       "$(fn-args "sshcommand-list")"       "$(fn-desc "sshcommand-list")"
  printf "  %-10s %-30s %s\\n" "help"       "$(fn-args "sshcommand-help")"       "$(fn-desc "sshcommand-help")"
  printf "  %-10s %-30s %s\\n" "version"    "$(fn-args "sshcommand-version")"    "$(fn-desc "sshcommand-version")"
}

sshcommand-version() {
  declare desc="Shows version"
  echo "sshcommand ${SSHCOMMAND_VERSION}"
}

main() {
  if [[ -z "$1" ]]; then
    sshcommand-help "$@"
    exit 1
  fi

  local cmd="sshcommand-$1"
  shift 1

  if declare -f "$cmd" > /dev/null; then
    $cmd "$@"
  else
    log-fail "Invalid command"
  fi
}

# shellcheck disable=SC2128
if [[ "$0" == "$BASH_SOURCE" ]]; then
  main "$@"
fi
